Source code for binarycpython.utils.population_extensions.cache

"""
File containing the class extension for the population object that contains cache functionality

Module containing (e.g. LRU) cache functionality for binary_c-python.

We use cachetools when possible because this allows us to set up the
cache of the appropriate size for the task in the population_options dict.
Please see the LRU_* options in there.
"""

# pylint: disable=E1101

import contextlib
import getpass
import importlib
import os
import time

import cachetools


[docs]class cache: """ Class extension for the population object that contains cache functionality """ def __init__(self, **kwargs): """ Init function for the spacing_functions class """ return # def default_cache_dir(self): # """ # Return a default cache directory path, or None if we cannot find one. # """ # error_string = "__*ERR*__" # string that cannot be a path # for path in [ # os.path.join(os.environ.get("HOME", error_string), ".cache", "binary_c"), # os.path.join(os.environ.get("TMP", error_string), "cache"), # ]: # if error_string not in path and os.path.isdir(path): # return path # return None
[docs] def default_cache_dir(self): """ Return a default cache directory path for binary_c-python, or None if we cannot find one. This is used in population_options_defaults.py """ error_string = "__*ERR*__" # string that cannot be a path for path in [ os.path.join(os.environ.get("HOME", error_string), ".cache"), os.path.join(os.environ.get("TMP", error_string), "cache"), os.path.join("var", "tmp", getpass.getuser(), "cache"), ]: if error_string not in path and os.path.isdir(path): return os.path.join(path, "binary_c") return None
[docs] class NullCache(cachetools.Cache): """ A cachetools cache object that does as little as possible and never matches. """ def __init__(self, *args, **kwargs): """ Init function for the spacing_functions class TODO: is this class necesarry to be defined *within* the cache class? can't it just be outside? """ return None
[docs] def popitem(self): """ pop function placeholder """ return # do nothing
[docs] def __getitem__(self, key): """ getter function placeholder """ return self.__missing__(key)
[docs] def __setitem__(self, key, value): """ Setter function placeholder """ return
[docs] def __delitem__(self, key): """ deleter function placeholder """ return
[docs] def setup_function_cache(self, vb=False, cachetype=None): """ Function to wrap binary_c-python's functions in function cache. The functions listed in self.population_options['function_cache_functions'] are given caches of size self.population_options['function_cache_size'][func] Args: None """ # add our custom NullCache to the cachetools selection setattr(cachetools, "NullCache", self.NullCache) if not self.population_options["function_cache"]: # no function cache: set all to NullCache # TODO: This cachetype(Nullcache) is wrong. for func in self.population_options["function_cache_functions"].keys(): self.function_cache[func] = cachetype(self.NullCache) for func in self.population_options["function_cache_functions"].keys(): (maxsize, cachetype, testargs) = self.population_options[ "function_cache_functions" ].get(func) # which cache should we use? if cachetype: # use type passed in, if given usecachetype = cachetype elif not self.population_options["function_cache"]: # function cache is disabled, use NoCache usecachetype = "NoCache" else: if cachetype is None: # use the default type usecachetype = self.population_options[ "function_cache_default_type" ] else: # use type passed in usecachetype = cachetype self.vb_info( "Setup cache for func {func} : maxsize={maxsize}, cachetype={cachetype}, testargs={testargs}-> use {usecachetype}".format( func=func, maxsize=maxsize, cachetype=cachetype, testargs=testargs, usecachetype=usecachetype, ) ) if usecachetype == "TTLCache": extra_cacheargs = [self.population_options["function_cache_TTL"]] else: extra_cacheargs = [] # detect if the function is already wrapped x = func.split(".") modulename = "binarycpython.utils.population_extensions." + x[0] module = importlib.import_module(modulename) # noqa: F841 _method = eval( "module.{}.{}".format(x[0], x[1]) ) # TODO: we can do this differently with some .get call instead of eval _wrapped = getattr(_method, "__wrapped__", False) # if function is wrapped... if _wrapped and id(_method) != id(_wrapped): # save the wrapped function (this calls the cache) if func not in self.cached_function_cache: self.cached_function_cache[func] = _method self.original_function_cache[func] = _wrapped if usecachetype == "NoCache": # unwrap if we're after NoCache _code = "module.{}.{} = _wrapped".format(x[0], x[1]) exec(_code) else: # function isn't wrapped, which means it was previously # unwrapped, so rewrap it if not using NoCache if usecachetype != "NoCache" and func in self.cached_function_cache: _code = 'module.{}.{} = self.cached_function_cache["{}"]'.format( x[0], x[1], func ) exec(_code) # check we're not still wrapped _method = eval("module" + "." + x[0] + "." + x[1]) _wrapped = getattr(_method, "__wrapped__", False) # if NoCache (explicity use no cache), just use NullCache # (it's never actually set) if usecachetype == "NoCache": cachetools_func = getattr(cachetools, "NullCache") else: cachetools_func = getattr(cachetools, usecachetype) if maxsize == 0: maxsize = self.population_options["function_cache_default_maxsize"] self.vb_info( "Make function cache for func {func}, maxsize {maxsize}".format( func=func, maxsize=maxsize ) ) # set up cache function args if maxsize is None: args = [2] else: args = [maxsize] args += extra_cacheargs # clear any existing cache if func in self.caches: try: self.caches[func].cache_clear() except: pass del self.caches[func] # set up new cache using the appropriate cachetools function if usecachetype != "NoCache": self.caches[func] = cachetools_func(*args)
[docs] def test_caches(self, dt=5.0): """ Function to test cache speeds of the functions that binary_c-python automatically caches. Args: dt (default 5) in seconds the length of each test. Long is more accurate, but takes longer. """ # loop lists cachetypes = ("NoCache", "NullCache", "FIFOCache", "LRUCache", "TTLCache") functions = self.population_options["function_cache_functions"].keys() maxsizes = (0, 1, 2, 4, 8, 16, 32, 64, 128, 256) self.population_options["function_cache"] = True for n, func in enumerate(functions): self.vb_debug("Cache speed test of function {func}".format(func=func)) self.vb_debug("{:18s}".format(""), end="") for x, maxsize in enumerate(maxsizes): self.vb_debug("{:>9s}".format(str(maxsize)), end="") self.vb_debug("") best = 0 best_type = None best_maxsize = None for y, type in enumerate(cachetypes): self.vb_debug("{:18s}".format(type), end="") self.population_options["function_cache_default_type"] = type self.setup_function_cache() (maxsize, cachetype, testargs) = self.population_options[ "function_cache_functions" ].get(func) # TODO: Make this part better: needs to be able to handle any depth x = func.split(".") modulename = ( # noqa: F841 "binarycpython.utils.population_extensions." + x[0] ) # noqa: F841 # module = importlib.import_module(modulename) _method = eval("module.{}.{}".format(x[0], x[1])) if testargs: def _func_wrap(*args, **kwargs): """ wrap to return args and kwargs TODO: i think this function can be defined elsewhere """ return (args, kwargs) args, kwargs = eval("_func_wrap({})".format(testargs)) for x, maxsize in enumerate(maxsizes): if type == "NoCache" and maxsize > 0: continue # redirect stdout to prevent lots of output with contextlib.redirect_stdout(None): # loop for dt seconds tfin = dt + time.time() count = 0 try: while time.time() < tfin: _method(self, *args, **kwargs) count += 1 # TODO: specify the exception except Exception as e: self.vb_error("Cache call failed:", e) self.exit(1) if count < 99999: self.vb_debug("{:9d}".format(count), end="") else: self.vb_debug("{:9.2e}".format(float(count)), end="") if count > best: best = count best_type = type best_maxsize = maxsize self.vb_info( "Best cache type {type} with maxsize {maxsize}\n".format( type=best_type, maxsize=best_maxsize ) )
""" Cache speed test of function distribution_functions.powerlaw_constant 0 1 2 4 8 16 32 64 128 256 NoCache 6.28e+07 NullCache 6.39e+07 6.40e+07 6.41e+07 6.39e+07 6.44e+07 6.43e+07 6.37e+07 6.40e+07 6.38e+07 6.40e+07 FIFOCache 6.41e+07 6.37e+07 6.40e+07 6.39e+07 6.40e+07 6.37e+07 6.41e+07 6.40e+07 6.41e+07 6.40e+07 LRUCache 6.42e+07 6.41e+07 6.42e+07 6.41e+07 6.38e+07 6.43e+07 6.41e+07 6.43e+07 6.40e+07 6.41e+07 TTLCache 6.41e+07 6.35e+07 6.37e+07 6.39e+07 6.37e+07 6.42e+07 6.39e+07 6.38e+07 6.37e+07 6.38e+07 Best cache type NullCache with maxsize 8 Cache speed test of function distribution_functions.calculate_constants_three_part_powerlaw 0 1 2 4 8 16 32 64 128 256 NoCache 1.44e+07 NullCache 9.13e+06 9.18e+06 9.20e+06 9.21e+06 9.20e+06 9.12e+06 9.18e+06 9.18e+06 9.15e+06 9.12e+06 FIFOCache 2.53e+07 2.52e+07 2.51e+07 2.50e+07 2.51e+07 2.52e+07 2.52e+07 2.52e+07 2.52e+07 2.51e+07 LRUCache 1.62e+07 1.62e+07 1.62e+07 1.62e+07 1.62e+07 1.62e+07 1.62e+07 1.62e+07 1.62e+07 1.62e+07 TTLCache 1.43e+07 1.43e+07 1.43e+07 1.43e+07 1.43e+07 1.44e+07 1.42e+07 1.43e+07 1.43e+07 1.43e+07 Best cache type FIFOCache with maxsize 0 Cache speed test of function distribution_functions.gaussian_normalizing_const 0 1 2 4 8 16 32 64 128 256 NoCache 64183 NullCache 64340 64339 64544 64260 64491 64382 64400 63974 63954 64338 FIFOCache 2.62e+07 2.62e+07 2.62e+07 2.61e+07 2.61e+07 2.59e+07 2.61e+07 2.59e+07 2.57e+07 2.59e+07 LRUCache 1.66e+07 1.66e+07 1.65e+07 1.66e+07 1.65e+07 1.65e+07 1.64e+07 1.65e+07 1.64e+07 1.65e+07 TTLCache 1.42e+07 1.44e+07 1.42e+07 1.44e+07 1.43e+07 1.43e+07 1.42e+07 1.44e+07 1.42e+07 1.44e+07 Best cache type FIFOCache with maxsize 1 Cache speed test of function spacing_functions.const_linear 0 1 2 4 8 16 32 64 128 256 NoCache 1.22e+06 NullCache 1.05e+06 1.05e+06 1.06e+06 1.05e+06 1.05e+06 1.06e+06 1.05e+06 1.05e+06 1.05e+06 1.05e+06 FIFOCache 2.85e+07 2.85e+07 2.86e+07 2.85e+07 2.84e+07 2.85e+07 2.84e+07 2.84e+07 2.85e+07 2.81e+07 LRUCache 1.77e+07 1.79e+07 1.73e+07 1.73e+07 1.76e+07 1.79e+07 1.76e+07 1.74e+07 1.74e+07 1.72e+07 TTLCache 1.46e+07 1.49e+07 1.50e+07 1.53e+07 1.51e+07 1.53e+07 1.52e+07 1.51e+07 1.47e+07 1.50e+07 Best cache type FIFOCache with maxsize 2 Cache speed test of function spacing_functions.const_int 0 1 2 4 8 16 32 64 128 256 NoCache 4.23e+07 NullCache 1.65e+07 1.66e+07 1.65e+07 1.64e+07 1.66e+07 1.65e+07 1.59e+07 1.59e+07 1.65e+07 1.64e+07 FIFOCache 2.86e+07 2.86e+07 2.87e+07 2.86e+07 2.84e+07 2.86e+07 2.81e+07 2.79e+07 2.78e+07 2.85e+07 LRUCache 1.78e+07 1.78e+07 1.77e+07 1.75e+07 1.77e+07 1.78e+07 1.78e+07 1.78e+07 1.74e+07 1.75e+07 TTLCache 1.55e+07 1.54e+07 1.55e+07 1.54e+07 1.55e+07 1.49e+07 1.52e+07 1.51e+07 1.52e+07 1.54e+07 Best cache type NoCache with maxsize 0 Cache speed test of function spacing_functions.const_ranges 0 1 2 4 8 16 32 64 128 256 NoCache 2.54e+05 NullCache 2.25e+05 2.25e+05 2.24e+05 2.25e+05 2.25e+05 2.25e+05 2.25e+05 2.26e+05 2.25e+05 2.26e+05 FIFOCache 2.58e+07 2.55e+07 2.53e+07 2.54e+07 2.56e+07 2.57e+07 2.56e+07 2.57e+07 2.58e+07 2.58e+07 LRUCache 1.62e+07 1.63e+07 1.62e+07 1.62e+07 1.61e+07 1.62e+07 1.62e+07 1.62e+07 1.61e+07 1.63e+07 TTLCache 1.41e+07 1.43e+07 1.42e+07 1.42e+07 1.40e+07 1.42e+07 1.42e+07 1.43e+07 1.40e+07 1.43e+07 Best cache type FIFOCache with maxsize 128 Cache speed test of function spacing_functions.gaussian_zoom 0 1 2 4 8 16 32 64 128 256 NoCache 24703 NullCache 24872 24935 24927 24896 24968 24964 24882 24840 24873 24913 FIFOCache 2.54e+07 2.54e+07 2.54e+07 2.54e+07 2.53e+07 2.52e+07 2.53e+07 2.51e+07 2.52e+07 2.52e+07 LRUCache 1.63e+07 1.63e+07 1.63e+07 1.64e+07 1.63e+07 1.64e+07 1.63e+07 1.63e+07 1.63e+07 1.63e+07 TTLCache 1.43e+07 1.43e+07 1.42e+07 1.42e+07 1.43e+07 1.42e+07 1.43e+07 1.43e+07 1.43e+07 1.43e+07 Best cache type FIFOCache with maxsize 0 """